#include "borwein.h"
#include "utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <gmp.h>

//-----------------------------------------------
// Sequencial
//-----------------------------------------------

void borwein_sequencial (long int n) {

    long int i;
    mpf_t a, b, c, d, e, F, t, sum;

	#ifdef CALCULATE_TIME
    double time = clock();
	#endif

    mpf_init (a);
    mpf_init (b);
    mpf_init (c);
    mpf_init (d);
    mpf_init (e);
    mpf_init (t);
    mpf_init (sum);
    mpf_init (F);
    mpf_set_d (F, 0.0625);

    mpf_set_ui(e, 1);

    for (i = 0; i < n; ++i) {

        // 8t
        long t8 = 8*i;

        // a = 4/(8t+1)
        mpf_set_ui (a, t8+1);
        mpf_ui_div (a, 4, a);

        // b = 2/(8t+4)
        mpf_set_ui (b, t8+4);
        mpf_ui_div (b, 2, b);

        // c = 1/(8t+5)
        mpf_set_ui (c, t8+5);
        mpf_ui_div (c, 1, c);

        // d = 1/(8n+6)
        mpf_set_ui (d, t8+6);
        mpf_ui_div (d, 1, d);

        // a = a-b-c-d
        mpf_sub (a, a, b);
        mpf_sub (a, a, c);
        mpf_sub (a, a, d);

        // a = a*e
        mpf_mul (a, a, e);

        // e = (1/16)^t --> e = (1/16) * e
        mpf_mul (e, e, F);

        mpf_add (sum, sum, a);

        mpf_add_ui (t, t, 1);
    }

	#ifdef CALCULATE_TIME
    time = (clock() - time) / CLOCKS_PER_SEC;
	#endif

    // Exibir resultado
    gmp_printf("%.7Ff\n", sum);

	#ifdef CALCULATE_TIME
    printf("Time: %lfs\n", time);
	#endif

    mpf_clear (a);
    mpf_clear (b);
    mpf_clear (c);
    mpf_clear (d);
    mpf_clear (e);
    mpf_clear (t);
    mpf_clear (sum);
    mpf_clear (F);
}


//-----------------------------------------------
// Paralelo
//-----------------------------------------------

struct borweinData {
    mpf_t sum;
    long int a, b;
    char mainth; // Indica se eh a thread principal
};

void* borwein_thread (void* data) {

    long int i;
    mpf_t a, b, c, d, e, F;

    struct borweinData* p_bd = (struct borweinData*) data;

    mpf_init (p_bd->sum);

    mpf_init (a);
    mpf_init (b);
    mpf_init (c);
    mpf_init (d);
    mpf_init (e);
    mpf_init (F);
    mpf_set_d (F, 0.0625); // 1/16

    // e = (1/16)^(t-1)
    if (p_bd->a != 0)
        mpf_pow_ui(e, F, p_bd->a - 1);
    else
        mpf_set_ui(e, 1);

    for (i = p_bd->a; i < p_bd->b; ++i) {

        // 8t
        long t8 = 8*i;

        // a = 4/(8t+1)
        mpf_set_ui (a, t8+1);
        mpf_ui_div (a, 4, a);

        // b = 2/(8t+4)
        mpf_set_ui (b, t8+4);
        mpf_ui_div (b, 2, b);

        // c = 1/(8t+5)
        mpf_set_ui (c, t8+5);
        mpf_ui_div (c, 1, c);

        // d = 1/(8n+6)
        mpf_set_ui (d, t8+6);
        mpf_ui_div (d, 1, d);

        // a = a-b-c-d
        mpf_sub (a, a, b);
        mpf_sub (a, a, c);
        mpf_sub (a, a, d);

        // a = a*e
        mpf_mul (a, a, e);

        // e = (1/16)^t --> e = (1/16) * e
        mpf_mul (e, e, F);

        // Acumular em sum
        mpf_add (p_bd->sum, p_bd->sum, a);
    }

    mpf_clear (a);
    mpf_clear (b);
    mpf_clear (c);
    mpf_clear (d);
    mpf_clear (e);
    mpf_clear (F);

    // Thread principal nao deve chamar pthread_exit
    if (!p_bd->mainth)
        pthread_exit(NULL);

    return NULL;
}

void borwein_paralelo (long int n, int threads) {

    struct borweinData *p_bd;
    pthread_t *threads_v;
    long int intervalo;
    int err, t;
    mpf_t sum;

    // Alocar espaco para threads e dados para a thread
    p_bd = (struct borweinData*) malloc(sizeof(struct borweinData)*threads);
    threads_v = (pthread_t*) malloc(sizeof(pthread_t)*threads);

	#ifdef CALCULATE_TIME
    double time = clock();
	#endif

    intervalo = n/threads;

    // Gerar threads
    for(t = 0; t < threads-1; t++) {

        p_bd[t].a = t*intervalo;
        p_bd[t].b = (t+1)*intervalo;
        p_bd[t].mainth = 0;

        err = pthread_create(&threads_v[t], NULL, borwein_thread, (void *) &p_bd[t]);

        if (err) {
            printf("ERRO: %d\n", err);
            exit(-1);
        }
    }

    // Executar ultima etapa na thread principal
    p_bd[t].a = t*intervalo;
    p_bd[t].b = (t+1)*intervalo + n%threads;
    p_bd[t].mainth = 1;

    borwein_thread((void*)&p_bd[t]);

    // Somar resultado dessa thread
    mpf_init(sum);
    mpf_add(sum, sum, p_bd[t].sum);
    mpf_clear(p_bd[t].sum);

    // Somar resultados das outras threads
    for(t = 0; t < threads-1; t++) {
        pthread_join (threads_v[t], NULL);
        mpf_add(sum, sum, p_bd[t].sum);
        mpf_clear(p_bd[t].sum);
    }

	#ifdef CALCULATE_TIME
    time = (clock() - time) / CLOCKS_PER_SEC;
	#endif

    // Exibir resultado
    gmp_printf("%.7Ff\n", sum);

	#ifdef CALCULATE_TIME
    printf("Time: %lfs\n", time);
	#endif

    free (p_bd);
    free (threads_v);
    mpf_clear(sum);
}
